package BitCreekTracker; /**
 * @author Ivan Litsvinenka & Emanuele Ricci
 * Questa classe rappresenta i servizi che il Tracker offre ai vari Peer e i meccanismi interni per la gestione
 * dei Torrents
 *
 */

import BitCreekLib.*;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TrackerServices implements IRMITracker {

    /**
     * Lista delle porte gia' bindate su un determinato torrent
     */
    private ArrayList<Integer> portsUsed;

    /**
     * HashTable che raccoglie tutte le informazioni relative ai torrents pubblicati
     */
    public static Hashtable<String, TorrentMetaDati> tht;

    /**
     * Massimo e minimo numero delle porte utilizzabili per bindare un torrent
     */
    private int minPort, maxPort;

    /**
     * Generatore random di porte
     */
    private Random randomGenerator;

    /**
     * I threadpool che gestiscono le connessioni TCP e UDP del Tracker
     */
    private ExecutorService tpUDP;
    private ExecutorService tpTCP;

    public void dumpHT () {
        System.out.println ( tht.toString () );
    }

    /**
     * Costruttore di TrackerServices
     * @throws java.rmi.RemoteException
     */
    public TrackerServices () throws RemoteException {
        super ();
        tht = new Hashtable<String, TorrentMetaDati> ();
        portsUsed = new ArrayList<Integer> ();
        this.tpUDP = Executors.newCachedThreadPool ();
        this.tpTCP = Executors.newCachedThreadPool ();
        this.randomGenerator = new Random ();
        minPort = 10000;
        maxPort = 14000;
    }

    /**
     * Generatore di porte random
     *
     * @return Una porta random fra minPort e maxPort
     */
    private int randomPort () {
        int r;
        int range = this.maxPort - this.minPort + 1;
        r = randomGenerator.nextInt ( range );
        return r + this.minPort;
    }

    /**
     * Questo metodo crea il pattern da utilizzare nell'espressione regolare per
     * la ricerca dei torrent all'interno del HashTable del Tracker
     *
     * @param keywords Lista di parole chiave da ricercare
     * @return Il pattern creato utilizzando le keywords passate
     */
    public String createPattern ( String[] keywords ) {
        String ret = "^.*";
        int i;
        for ( i = 0; i < keywords.length; i++ ) {
            keywords[ i ] = keywords[ i ].trim ();
            ret += keywords[ i ] + ".*";
        }
        ret += "$";
        return ret;
    }

    /**
     * Metodo utilizzato dal Tracker per effettuare la callback al Peer che ha pubblicato un determinato torrent
     *
     * @param peer        L'interfaccia del peer che ha pubblicato il torrent
     * @param torrentName Il nome del torrent pubblicato
     * @param host
     * @throws java.rmi.RemoteException
     */
    private synchronized void doCallbacks ( IRMIPeer peer, String torrentName, Host host ) throws RemoteException {
        System.out.println ( "Starting callbacks. Host: " +host.toString() );
        peer.notifyMe ( torrentName, host  );
    }


    public Ports getTorrentPorts ( String torrentName ) throws RemoteException {
        if ( !tht.containsKey ( torrentName ) ) return null;
        else {
            return tht.get ( torrentName ).getTrackerPorts ();
        }
    }


    public void publishTorrent ( InetAddress firstSeeder, int firstSeederPort, MetaInfo tmi, IRMIPeer callbackClient ) throws RemoteException {
        int trackerPort;
        do {
            trackerPort = randomPort ();
        } while ( this.portsUsed.contains ( trackerPort ) );
        this.portsUsed.add ( trackerPort );

        tmi.setTrackerPorts( new Ports (trackerPort, trackerPort) );
        try {
			tmi.setTrackerAddress( InetAddress.getLocalHost() );
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
        /* crea thread TCP e UDP */
        tht.put ( tmi.getName (), new TorrentMetaDati ( new Host ( firstSeeder, firstSeederPort ), callbackClient, tmi, new Ports ( trackerPort, trackerPort ) ) );
        this.tpTCP.execute ( new TrackerTCPThread ( trackerPort ) );
        this.tpUDP.execute ( new TrackerUDPThread ( trackerPort ) );
        dumpHT ();
    }


    public MetaInfo getTorrent ( String torrentName, Host host ) throws RemoteException {
        if ( !tht.containsKey ( torrentName ) ) return null;
        else {
            TorrentMetaDati tmd = tht.get ( torrentName );
            MetaInfo tmi = tmd.getMetaInfo ();
            if ( tmd.getIFirstSeeder() != null ) {
                try {
                    doCallbacks ( tmd.getIFirstSeeder (), tmi.getName (), host );
                } catch ( Exception e ) {
                	// Problema di connesione con il primo seeder. Il seeder si e' disconesso.
                    tmd.setIFirstSeeder(null);
                }
            }

            return tmi;
        }
    }


    public ArrayList<MetaInfo> searchTorrent ( String keyphrase ) throws RemoteException {
        String key, torrentName;
        MetaInfo torrent;
        ArrayList<MetaInfo> matches = new ArrayList<MetaInfo> ();
        /* Da cambiare la split */
        String pattern = createPattern ( keyphrase.split ( " " ) );
        Enumeration<String> keys = tht.keys ();

        while ( keys.hasMoreElements () ) {
            key = keys.nextElement ();
            torrent = tht.get ( key ).getMetaInfo ();
            torrentName = torrent.getName ();


            if ( torrentName.matches(pattern) && tht.get( key ).getPeers().size() > 0 )
			   matches.add(torrent);
		}
		return matches;
	}

}
